﻿using System.Collections.Generic;
using System.Collections.Specialized;
using System.Configuration;
using System.Globalization;
using System.Linq;
using System.Threading;
using System.Web.UI;
using System.Xml;
using BlogsAPI.Interfaces;
using Newtonsoft.Json.Linq;
using RssToolkit.Rss;

namespace BlogsAPI.Sites
{
    using System.Web;
    using System;
    using System.Diagnostics;
    using System.IO;
    using System.Net;
    using System.Text;
    using System.Text.RegularExpressions;
    using Abstract;

//5: '240p FLV h.263',
//    17: '144p 3GP mpeg4 simple',
//    18: '360p MP4 h.264 baseline',
//    22: '720p MP4 h.264 high',
//    34: '360p FLV h.264 main',
//    35: '480p FLV h.264 main',
//    36: '240p 3GP mpeg4 simple',
//    37: '1080p MP4 h.264 high',
//    43: '360p WebM vp8',
//    44: '480p WebM vp8',
//    45: '720p WebM vp8',
//    46: '1080p WebM vp8',
//    82: '360p MP4 h.264 3D',
//    84: '720p MP4 h.264 3D',
//    100: '360p WebM vp8 3D',
//    102: '720p WebM vp8 3D'
    /// <summary>
    /// http://habrahabr.ru/post/137496/
    /// </summary>
    public enum VideoFormat : int
    {
        FLV_320x240 = 5,
        FLV_640x360 = 34,
        FLV_854x480 = 35,

        MP4_480x360 = 18,//not sure , could 1
        MP4_1280x720 = 22,
        MP4_1920x1080 = 37,
        MP4_4096x1714 = 38,

        WEBM_640x360 = 43,
        WEBM_854x480 = 44,
        WEBM_1280x720 = 45,

        Mobile_176x144 = 17,//3gpp
        Mobile_176x144_mono = 13,
        Mobile_240x = 36,
        //wtf = itag= 137
    }

    /// <summary>
    /// Видеохостинг
    /// </summary>
    [BlogServiceAPI(Description = "YouTube", Link = "http://youtube.com")]
    public class YouTube : BlogServer , IVideoHost
    {
        /// <summary>
        /// Уникальный ключ для простых запросов
        /// </summary>
        public String APIkey = null;//массив из двух ключей, основного и запасного

        private static readonly Regex Validation = new Regex(@"^https?://(?:[-_\w]+\.)?(?:youtube\.com|youtu\.be)", Regexoptions);

        /// <summary>
        /// Регулярка для обнаружения ссылок на ютуб в тексте, важно, чтобы вначале не стояло ^
        /// </summary>
        public static readonly Regex rYoutube =
            new Regex(@"https?://(?:[-_\w]+\.)?(?:youtube\.com|youtu\.be)/(\w+/|watch.+?v=)?([-_\w]+)(?:\b|$)", Regexoptions);

        #region interface: BlogServer

        [DebuggerStepThrough]
        public YouTube()
        {
            Encoding = Encoding.UTF8;
            if (ConfigurationManager.AppSettings["GooglePlus"] != null)
            {
                //может быть несколько ключей, разделенных запятыми
                APIkey = ConfigurationManager.AppSettings["GooglePlus"].Split(',')[0];
            }
        }

        /// <summary>
        /// Убираем параметры
        /// </summary>
        /// <param name="videolink"></param>
        /// <returns></returns>
        public override string ToCanonicalLink(string videolink)
        {
            string usercode;
            if (ValidateLink(videolink, out usercode))
            {
                videolink = YouTube.GetYoutubeCanonical(usercode);
            }
            else
            {
                //ошибочно, но пускай будет
                videolink = base.ToCanonicalLink(videolink);
            }
            return videolink;
        }

        /// <summary>
        /// http://www.t30p.ru/Youtube.aspx
        /// </summary>
        public override bool HasRating
        {
            get { return true; }
        }

        /// <summary>
        /// проверка, является ли передаваемый урл чатью этого сервиса
        /// </summary>
        public override Boolean ValidateLink(String sLink)
        {
            return Validation.Match(sLink).Success;
        }

        /// <summary>
        /// проверка, является ли передаваемый урл чатью этого сервиса
        /// </summary>
        /// <param name="sLink">ссылка для проверки</param>
        /// <param name="userName">Имя пользователя на блогохостинге</param>
        /// <param name="bType">Тип блога - пользователь или сообщество</param>
        public override Boolean ValidateLink(String sLink, out String userName, out BlogType bType)
        {
            //http://youtube.com/watch?v=3uyVySBWW94
            bType = BlogType.Unknown;
            Match m = rYoutube.Match(sLink);
            if (m.Success)
            {
                //http://www.youtube.com/user/MrKvascoff
                //http://www.youtube.com/channel/UCjvxgzf8yt1ycy1h53ces8g
                bType = m.Groups[1].Value.ToLower()=="user/"?BlogType.User:BlogType.Unknown;
                userName = m.Groups[2].Value;
                if (m.Groups[1].Value.ToLower() == "channel/" && m.Groups[2].Value.Length > 2)
                {
                    bType = BlogType.Community;
                    userName = m.Groups[2].Value.Substring(2);//skip UC (user channel)
                }
            }
            else
            {
                userName = String.Empty;
            }
            return m.Success;
        }

        /// <summary>
        /// Получение ссылки на блог блогера
        /// </summary>
        /// <param name="userName">ник блогера</param>
        /// <param name="btype">Тип блога - пользователь или сообщество</param>
        public override String GetBlogLink(string userName, BlogType btype)
        {
            if (userName == null) userName = String.Empty;
            if (btype == BlogType.Community || userName.Length == 22)
            {
                return String.Format("http://youtube.com/channel/UC{0}", userName);
            }else if(userName.StartsWith("UC"))
            {
                return String.Format("http://youtube.com/channel/{0}", userName);
            }
            return String.Format("http://youtube.com/{0}",userName);
        }

        #endregion

        private XmlNamespaceManager GetNamespaces(XmlNameTable nameTable)
        {
            var namespaceManager = new XmlNamespaceManager(nameTable);
                            namespaceManager.AddNamespace("def", "http://www.w3.org/2005/Atom");
                            namespaceManager.AddNamespace("media", "http://search.yahoo.com/mrss/");
                            namespaceManager.AddNamespace("yt", "http://gdata.youtube.com/schemas/2007");
                            namespaceManager.AddNamespace("gd", "http://schemas.google.com/g/2005");
            return namespaceManager;
        }

        /// <summary>
        /// Получение данных об одном пользователе
        /// </summary>
        /// <param name="userName"></param>
        /// <param name="part">Какие данные запросить</param>
        /// <example>https://www.googleapis.com/youtube/v3/channels?part=snippet%2Cstatistics&id=UC__10lnzsFZ43U-DprYvRRA&key={YOUR_API_KEY}</example>
        /// <returns></returns>
        private JObject GetUser(string userName, string part = "snippet,statistics,contentDetails")
        {
            Uri postlink;
            if (Uri.TryCreate(String.Format("https://www.googleapis.com/youtube/v3/channels?part={3}&{0}={1}&key={2}",
                userName.StartsWith("UC")?"id":"forUsername",
                userName, 
                APIkey,
                HttpUtility.UrlEncode(part)
                ), UriKind.Absolute, out postlink))
            {
                var myHttpWebRequest =
                    (HttpWebRequest)WebRequest.Create(postlink);
                //Common.MakeRequest(postlink.OriginalString);
                //myHttpWebRequest.AllowAutoRedirect = false;
                using (var myHttpWebResponse = (HttpWebResponse)myHttpWebRequest.GetResponse())
                {
                    if (myHttpWebResponse.StatusCode == HttpStatusCode.OK)
                    {
                        string reply = new StreamReader(myHttpWebResponse.GetResponseStream()).ReadToEnd();
                        return JObject.Parse(reply);
                    }
                    myHttpWebResponse.Close();
                }
            }
            return null;
        }

        /// <summary>
        /// Извлечение данных о пользователе
        /// </summary>
        /// <param name="userName"></param>
        /// <param name="getGooglePlus">нужно ли запрашивать доп.данные о пользователе</param>
        /// <returns></returns>
        public YouTubeStatistics GetStats(string userName, bool getGooglePlus = true)
        {
            var oRet = new YouTubeStatistics();
            try
            {   
                oRet.Username = userName;
                //делаем запрос к API
                var usr = GetUser(userName);
                if (usr != null && usr["items"] != null && usr["items"].Any())
                {
                    //только так, ибо ютуб зарезал уникальные имена
                    oRet.ID = usr["items"][0]["id"].Value<string>();
                    //Статистика(!)
                    if (usr["items"][0]["statistics"] != null)
                    {
                        oRet.totalUploadViews = usr["items"][0]["statistics"]["viewCount"].Value<ulong>();
                        oRet.Uploads = usr["items"][0]["statistics"]["videoCount"].Value<int>();
                        oRet.commentCount = usr["items"][0]["statistics"]["commentCount"].Value<int>();
                        oRet.subscriberCount = usr["items"][0]["statistics"]["subscriberCount"].Value<uint>();
                    }
                    //var selectSingleNode = xDoc.SelectSingleNode("//yt:statistics", GetNamespaces(xDoc.NameTable));
                    //if (selectSingleNode != null && selectSingleNode.Attributes != null)
                    //{
                    //    oRet.subscriberCount = uint.Parse(selectSingleNode.Attributes["subscriberCount"].Value);
                    //    oRet.totalUploadViews = ulong.Parse(selectSingleNode.Attributes["totalUploadViews"].Value);
                    //    oRet.viewCount = int.Parse(selectSingleNode.Attributes["viewCount"].Value);
                    //}
                    //Название
                    if (usr["items"][0]["snippet"] != null)
                    {
                        oRet.Name = usr["items"][0]["snippet"]["title"].Value<string>();
                        oRet.About = usr["items"][0]["snippet"]["description"].Value<string>();
                    }
                    ////Name
                    //selectSingleNode = xDoc.SelectSingleNode("//def:title", GetNamespaces(xDoc.NameTable));
                    //if (selectSingleNode != null)
                    //{
                    //    oRet.Name = selectSingleNode.InnerText;
                    //}
                    ////Description
                    //selectSingleNode = xDoc.SelectSingleNode("//def:content", GetNamespaces(xDoc.NameTable));
                    //if (selectSingleNode != null)
                    //{
                    //    oRet.About = selectSingleNode.InnerText;
                    //}
                    ////Gender
                    //selectSingleNode = xDoc.SelectSingleNode("//yt:gender", GetNamespaces(xDoc.NameTable));
                    //if (selectSingleNode != null)
                    //{
                    //    oRet.Gender = selectSingleNode.InnerText;
                    //}
                    ////Location
                    //selectSingleNode = xDoc.SelectSingleNode("//yt:location", GetNamespaces(xDoc.NameTable));
                    //if (selectSingleNode != null)
                    //{
                    //    oRet.Location = selectSingleNode.InnerText;
                    //}
                    ////Uploads
                    //XmlNodeList nodes = xDoc.SelectNodes("//gd:feedLink[@rel]", GetNamespaces(xDoc.NameTable));
                    //if (nodes != null)
                    //{
                    //    foreach (XmlNode node in nodes)
                    //    {
                    //        if (node.Attributes!=null && node.Attributes["rel"].Value.Contains("#user.uploads"))
                    //        {
                    //            oRet.Uploads = int.Parse(node.Attributes["countHint"].Value);
                    //            break;
                    //        }
                    //    }
                    //}
                    ////userName
                    //selectSingleNode = xDoc.SelectSingleNode("//yt:username", GetNamespaces(xDoc.NameTable));
                    //if (selectSingleNode != null)
                    //{
                    //    oRet.Username = selectSingleNode.InnerText;
                    //}
                    //else
                    //{
                    //    oRet.Username = userName;//входной параметр
                    //}

                    ////googlePlusUserId
                    if (usr["items"][0]["contentDetails"] != null)
                    {
                        if (getGooglePlus && usr["items"][0]["contentDetails"]["googlePlusUserId"] != null)
                        {
                            var googlePlusId = usr["items"][0]["contentDetails"]["googlePlusUserId"].Value<string>();
                            var gp = new GooglePlus();
                            var gpUser = gp.GetUser(googlePlusId);
                            if (gpUser != null)
                            {
                                //oRet.subscriberCount += (uint) gpUser.circledByCount; //число подписчиков
                                oRet.viewCount = gpUser.CircledByCount.Value; //число просмотров профиля (?)
                                if (!String.IsNullOrWhiteSpace(gpUser.Gender))
                                {
                                    oRet.Gender = gpUser.Gender.Substring(0, 1).ToLower();
                                }
                                if (String.IsNullOrEmpty(oRet.About))
                                {
                                    oRet.About = gpUser.AboutMe;
                                }
                            }
                        }
                        if (usr["items"][0]["contentDetails"]["relatedPlaylists"]?["uploads"] != null)
                        {
                            //relatedPlaylists   
                            oRet.UploadsPlayListID = usr["items"][0]["contentDetails"]["relatedPlaylists"]["uploads"].Value<string>();
                        }
                    }
                }
                else
                {
                    oRet.viewCount = -404;//для совместимости со старым API, сообщаем что удален и не найден
                }
            }
            catch (Exception e0)
            {
                Debug.Write(e0);
                if (e0 is WebException && ((WebException)e0).Response != null)
                {
                    oRet.viewCount = -(int)((HttpWebResponse)((WebException)e0).Response).StatusCode;
                    //указаываем число просмотров как -1
                }
            }
            return oRet;
        }

        /// <summary>
        /// Getting user PIC
        /// </summary>
        /// <param name="userName"></param>
        /// <param name="btype"></param>
        /// <returns></returns>
        public override string ExtractAvatar(string userName, BlogType btype)
        {
            try
            {
                //нужно только snippet
                var usr = GetUser(userName, "snippet");
                if (usr != null && usr["items"] != null && usr["items"].Any())
                {
                    return usr["items"][0]["snippet"]["thumbnails"]["default"]["url"].Value<string>();
                }
            }catch(Exception e1)
            {
                Debug.Write(e1);
            }
            return "";
        }

        /// <summary>
        /// Getting followers count
        /// </summary>
        /// <param name="userName"></param>
        /// <param name="bt"></param>
        /// <returns></returns>
        public override uint? HowManyReaders(string userName, BlogType bt)
        {
            try
            {
                var usr = GetUser(userName, "statistics");
                if (usr != null && usr["items"] != null && usr["items"].Any() && usr["items"][0]["statistics"]!=null)
                {
                    return usr["items"][0]["statistics"]["subscriberCount"].Value<uint>();
                }
            }catch(Exception e1)
            {
                Debug.Write(e1);
            }
            return null;
        }

        #region IData

        /// <summary>
        /// there is no RSS
        /// </summary>
        public override string GetRssLink(string userName, BlogType btype)
        {
            //at v3 API is no more
            return "";//String.Format("http://gdata.youtube.com/feeds/base/{0}/{1}/uploads?alt=rss&v=2&orderby=published", "users", userName);
        }

        /// <summary>
        /// there is no FOAF
        /// </summary>
        public override string GetFoafLink(string userName, BlogType btype)
        {
            return "";
        }

        /// <summary>
        /// Сервис получение данных в виде RSS
        /// </summary>
        /// <param name="userName">ник блогера</param>
        /// <param name="playlistID">Идентификатор набора видео, если есть</param>
        /// <param name="likesData">извлекать ли лайки от видео</param>
        /// <returns></returns>
        public RssDocument GetRss(string userName, string playlistID = null, bool likesData = false)
        {
            RssDocument oRet = null;
            try
            {
                if (String.IsNullOrEmpty(playlistID))
                {
                    var usrJson = GetUser(userName, "contentDetails");
                    playlistID = usrJson["items"][0]["contentDetails"]?["relatedPlaylists"]?["uploads"].Value<string>();
                }
                //получили данные по плейлисту
                if (!String.IsNullOrEmpty(playlistID))
                {
                    //ищем список видео
                    //https://www.googleapis.com/youtube/v3/playlistItems?part=snippet&playlistId=UUbW18JZRgko_mOGm5er8Yzg&key={YOUR_API_KEY}
                    Uri postlink;
                    if (Uri.TryCreate(String.Format("https://www.googleapis.com/youtube/v3/playlistItems?part=snippet&playlistId={0}&key={1}",
                        playlistID, APIkey
                        ), UriKind.Absolute, out postlink))
                    {
                        var myHttpWebRequest =
                            (HttpWebRequest)WebRequest.Create(postlink);
                        using (var myHttpWebResponse = (HttpWebResponse)myHttpWebRequest.GetResponse())
                        {
                            if (myHttpWebResponse.StatusCode == HttpStatusCode.OK)
                            {
                                oRet = new RssDocument
                                {
                                    Channel = new RssChannel(),
                                    Url = postlink.OriginalString
                                };
                                oRet.Channel.Items = new List<RssItemYandex>();
                                string reply = new StreamReader(myHttpWebResponse.GetResponseStream()).ReadToEnd();
                                var itemsData = JObject.Parse(reply);
                                var items = itemsData["items"];
                                JObject statsJSON = null;
                                //запрос дополнительно статистики по лайкам ?
                                if (likesData)
                                {
                                    var allvideos = items.Aggregate(String.Empty, (curr, i) =>
                                   curr + "," + i["snippet"]?["resourceId"]?["videoId"].Value<string>());

                                    if (Uri.TryCreate(String.Format("https://www.googleapis.com/youtube/v3/videos?id={0}&part=statistics&key={1}",
                                        allvideos.Trim(','), APIkey), UriKind.Absolute, out postlink))
                                    {
                                        var myHttpWebRequest2 =
                                            (HttpWebRequest)WebRequest.Create(postlink);
                                        //Common.MakeRequest(postlink.OriginalString);
                                        //myHttpWebRequest.AllowAutoRedirect = false;
                                        using (var myHttpWebResponse2 = (HttpWebResponse)myHttpWebRequest2.GetResponse())
                                        {
                                            if (myHttpWebResponse2.StatusCode == HttpStatusCode.OK)
                                            {
                                                string reply2 = new StreamReader(myHttpWebResponse2.GetResponseStream()).ReadToEnd();
                                                //банальный парсинг
                                                statsJSON = JObject.Parse(reply2);
                                                //foreach (var item in likesJSON["items"])
                                                //{
                                                //    var id = item["id"].Value<string>();
                                                //    var rssItem = oRet.Channel.Items.First(i => i.Guid.Text == id);
                                                //    rssItem.YaVisits24 = item["statistics"]["viewCount"].Value<int>();
                                                //    rssItem.YaLinks = item["statistics"]["likeCount"].Value<int>();
                                                //    rssItem.YaLinks24 = item["statistics"]["dislikeCount"].Value<int>();
                                                //    rssItem.YaComments = item["statistics"]["commentCount"].Value<int>();
                                                //}
                                            }
                                            myHttpWebResponse2.Close();
                                        }
                                    }
                                }

                                foreach (var item in items)
                                {
                                    var videoId = item["snippet"]?["resourceId"]?["videoId"].Value<string>();
                                    var rssItem = new RssItemYandex
                                    {
                                        Title = item["snippet"]?["title"].Value<string>(),
                                        Description = item["snippet"]?["description"].Value<string>(),
                                        PubDateParsed = item["snippet"]["publishedAt"].Value<DateTime>(),
                                        Link = GetYoutubeCanonical(videoId),
                                    };
                                    var statsItem = statsJSON?["items"].First(i => i["id"].Value<string>() == videoId);
                                    if (statsItem != null)
                                    {
                                        rssItem.YaVisits24 = statsItem["statistics"]["viewCount"].Value<int>();
                                        rssItem.YaLinks = statsItem["statistics"]["likeCount"].Value<int>();
                                        rssItem.YaLinks24 = statsItem["statistics"]["dislikeCount"].Value<int>();
                                        if (statsItem["statistics"]["commentCount"] != null)
                                        {
                                            rssItem.YaComments = statsItem["statistics"]["commentCount"].Value<int>();
                                        }
                                    }
                                    oRet.Channel.Items.Add(rssItem);
                                }

                            }
                            myHttpWebResponse.Close();
                        }
                    }
                    
                }
            }
            catch (Exception e2)
            {
                Debug.WriteLine(e2.Message);
            }
            return oRet;
        }

        #endregion

        #region IVideoHost

        /// <summary>
        /// просто ссылки на видео ютуба
        /// </summary>
        //public static readonly Regex rYoutubeVideo = new Regex(@"youtube\.com.*?\W(?:v|embed)\W([-_\w]+)(?:\W|$)|youtu\.be/([-_\w]+)\b",
        //    Regexoptions);

        /// <summary>
        /// Получение данных по ссылке
        /// Важно!!! Если ссылка содержит #, то проверяется также наличие статуса у видео
        /// Такая хитрость только ради экономии числа запросов
        /// </summary>
        /// <param name="link"></param>
        /// <returns></returns>
        public Tuple<VideoData, BlogPost> GetVideoData(Uri link)
        {
            var oRet = new Tuple<VideoData, BlogPost>(
                new VideoData(link),
                new BlogPost(link){Existance = false, Links = 0,Comments = 0});
            try
            {
                if(String.IsNullOrEmpty(APIkey)) throw new ArgumentNullException("GooglePlus API key at config section");
                Match v = rYoutube.Match(link.OriginalString);
                if (v.Success)
                {
                    string code = v.Groups[2].Value;
                    Uri postlink;
                    if (Uri.TryCreate(String.Format("https://www.googleapis.com/youtube/v3/videos?id={0}&part=snippet,statistics,recordingDetails,contentDetails{2}&key={1}",
                        code, APIkey, link.OriginalString.Contains('#')?",status": ",topicDetails"), UriKind.Absolute, out postlink))
                    {
                        // https://i.ytimg.com/vi/CvfsmEi06Z0/3.jpg - images
                        using (var client = new WebClient() { Encoding = Encoding.UTF8})
                        {
                            string reply = client.DownloadString(postlink);
                            ProcessYouTubeData2(code, reply, ref oRet);
                        }
                    }
                }
            }
            catch (Exception e1)
            {
                Debug.Write(e1);
                oRet.Item1.Title = e1.Message;//for debug
            }
            return oRet;
        }

        /// <summary>
        /// Обработка данных из Ютуба
        /// </summary>
        /// <param name="link">ссылка</param>
        /// <param name="reply">XML ответ</param>
        /// <param name="oRet">распарсенные данные</param>
        /// <exception>Can throw error</exception>
        [Obsolete("Since v3 API is no more supported")]
        public void ProcessYouTubeData(Uri link, string reply, ref Tuple<VideoData, BlogPost> oRet)
        {
            //попробуем как XML
            var xDoc = new XmlDocument();
            //xDoc.Schemas.Add("media", "http://search.yahoo.com/mrss/");
            xDoc.LoadXml(reply.Trim());
            var namespaceManager = GetNamespaces(xDoc.NameTable);
            //var namespaceManager = new XmlNamespaceManager(xDoc.NameTable);
            //namespaceManager.AddNamespace("def", "http://www.w3.org/2005/Atom");
            //namespaceManager.AddNamespace("media", "http://search.yahoo.com/mrss/");
            //namespaceManager.AddNamespace("yt", "http://gdata.youtube.com/schemas/2007");
            //namespaceManager.AddNamespace("gd", "http://schemas.google.com/g/2005");
            XmlNodeList thumbnails = xDoc.SelectNodes("//media:thumbnail", namespaceManager);
            string imagestoadd = String.Empty;
            if (thumbnails != null)
                foreach (XmlElement image in thumbnails)
                {
                    int width = int.Parse(image.Attributes["width"].Value);
                    if (width < 200)
                    {
                        imagestoadd += String.Format("<img src='{0}' width='{1}' height='{2}'/>",
                                                     image.Attributes["url"].Value,
                                                     image.Attributes["width"].Value,
                                                     image.Attributes["height"].Value);
                    }
                    else //картинка большая для замены.
                    {
                        oRet.Item1.Replace = String.Format(
                            "<a href=\"{3}\"><img src='{0}' width='{1}' height='{2}'/>[Видео]</a>",
                            image.Attributes["url"].Value, image.Attributes["width"].Value,
                            image.Attributes["height"].Value, link.OriginalString);
                    }
                }
            oRet.Item1.Images += imagestoadd;
            //просмотры
            XmlNode stats = xDoc.SelectSingleNode("//yt:statistics", namespaceManager);
            if (stats != null)
            {
                oRet.Item1.Views += int.Parse(stats.Attributes["viewCount"].Value);
            }
            //и в завершение - дата публикации
            XmlNode pub = xDoc.SelectSingleNode("//def:published", namespaceManager);
            if (pub == null || !DateTime.TryParse(pub.InnerText, out oRet.Item1.Published))
            {
                oRet.Item1.Published = DateTime.Now.AddMonths(-1);//чтобы не вставить текущий день
            }
            oRet.Item2.Published = oRet.Item1.Published;
            //имя канали или что-то там.
            XmlNode title = xDoc.SelectSingleNode("//def:title", namespaceManager);
            XmlNode description = xDoc.SelectSingleNode("//media:description", namespaceManager);
            if (title != null && description != null)
            {
                //    && (Common.IsRussian(title.InnerText)) || Common.IsRussian(description.InnerText))
                //{
                //если следующая строчка не сломается, то все будет хорошо.
                var channelname =
                    xDoc.SelectSingleNode("//def:author/def:uri", namespaceManager).InnerText.Split(
                        new[] { "/users/" }, StringSplitOptions.RemoveEmptyEntries)[1];
                var commentsfeed = xDoc.SelectSingleNode("//gd:feedLink", namespaceManager);
                int cmnts = commentsfeed == null || commentsfeed.Attributes == null
                                ? 0
                                : int.Parse(commentsfeed.Attributes["countHint"].Value);
                //<yt:duration seconds='899'/>
                XmlNode durationNode = xDoc.SelectSingleNode("//yt:duration", namespaceManager);
                if (durationNode != null)
                {
                    //выбираем большую длину ролика
                    oRet.Item1.Duration = uint.Parse(durationNode.Attributes["seconds"].Value);
                }

                //по идее тутже можем отправить на индексирование
                var keywords = xDoc.SelectSingleNode("//media:keywords", namespaceManager);
                oRet.Item2.Link = link;
                oRet.Item2.Existance = true;
                oRet.Item2.Views = oRet.Item1.Views;
                oRet.Item2.Links = 0;
                oRet.Item2.Comments = cmnts;
                oRet.Item2.SpecialParam = channelname;
                //////title
                oRet.Item2.Subject = title.InnerText.Replace("логгер", "логер");
                oRet.Item1.Title = oRet.Item2.Subject;
                //////description
                oRet.Item2.Body = description.InnerText.Replace("логгер", "логер");
                oRet.Item1.About = oRet.Item2.Body;
                //////
                oRet.Item2.Attributes = new PostAttributeCollection
                                        {
                                            new PostAttribute(PostParams.Tags,
                                                              keywords == null ? "" : keywords.InnerText),
                                            new PostAttribute(PostParams.MediaThumbnail, imagestoadd),
                                            new PostAttribute(PostParams.Length, oRet.Item1.Duration)
                                        };
                //linked
                //XmlNode response = xDoc.SelectSingleNode(
                //    "//def:link[@rel='http://gdata.youtube.com/schemas/2007#video.in-response-to']/@href",
                //    namespaceManager);
                //if (response != null)
                //{
                //    string[] arrs = response.Value.Split('/');
                //    if (arrs.Length > 1)
                //    {
                //        oRet.Item1.VideoResponse = GetYoutubeCanonical(arrs[arrs.Length - 1]);
                //        //TopTools.Azure.BlobStorage.Queue("postsforindex").AddMessage(
                //        //    new CloudQueueMessage(videoresponse));
                //    }
                //}
            }
        }

        /// <summary>
        /// Обработка данных из Ютуба
        /// </summary>
        /// <param name="code">id</param>
        /// <param name="reply">XML ответ</param>
        /// <param name="oRet">распарсенные данные</param>
        /// <exception>Can throw error</exception>
        private void ProcessYouTubeData2(string code, string reply, ref Tuple<VideoData, BlogPost> oRet)
        {
            JObject data = JObject.Parse(reply);
            if(!data["items"].Any()) return;//не найдено

            var item = data["items"][0];
            string imagestoadd = String.Empty;
            for (int i = 1; i < 4; i++)
            {
                imagestoadd += String.Format("<img src='{0}' width='{1}' height='{2}'/>",
                                                    "https://i.ytimg.com/vi/"+code+"/"+i+".jpg",
                                                    120, 90);
            }
            oRet.Item1.Images += imagestoadd;
            oRet.Item1.Replace = String.Format(
                            "<a href=\"{3}\"><img src='https://i.ytimg.com/vi/{0}/mqdefault.jpg' width='{1}' height='{2}'/>[Видео]</a>",
                            code, 320, 180, GetYoutubeCanonical(code));
            //просмотры
            var tags = item["snippet"]?["tags"] != null ? String.Join(",", item["snippet"]["tags"]) : String.Empty;
            if (item["statistics"] != null)
            {
                oRet.Item1.Views += item["statistics"]["viewCount"].Value<int>();
                oRet.Item2.Links = item["statistics"]["favoriteCount"].Value<int>();
                if (item["statistics"]["commentCount"] != null)
                {
                    oRet.Item2.Comments = item["statistics"]["commentCount"].Value<int>();
                }
                var likes = item["statistics"]["likeCount"].Value<int>();
                var dislikes = item["statistics"]["dislikeCount"].Value<int>();
                if (likes > 0)
                {
                    tags += ",+=" + likes;
                }
                if (dislikes > 0)
                {
                    tags += ",-=" + dislikes;
                }
                tags = tags.Trim(',');
            }
            ////и в завершение - дата публикации
            //XmlNode pub = xDoc.SelectSingleNode("//def:published", namespaceManager);
            //if (pub == null || !DateTime.TryParse(pub.InnerText, out oRet.Item1.Published))
            //{
            //    oRet.Item1.Published = DateTime.Now.AddMonths(-1);//чтобы не вставить текущий день
            //}
            if (item["snippet"] != null)
            {
                oRet.Item1.Published = item["snippet"]["publishedAt"].Value<DateTime>();
                var title = item["snippet"]["title"].Value<string>();
                var description = item["snippet"]["description"].Value<string>();
                if (title != null && description != null
                    //&& (Common.IsRussian(title)) || Common.IsRussian(description)
                    )
                {
                    string channelId = item["snippet"]["channelId"].Value<string>();
                    string channelTitle = item["snippet"]["channelTitle"].Value<string>();//название канала
                    oRet.Item2.Link = new Uri(GetYoutubeCanonical(code));
                    oRet.Item2.Existance = true;
                    oRet.Item2.Views = oRet.Item1.Views;
                    oRet.Item2.SpecialParam = channelId;
                    //////title
                    oRet.Item2.Subject = title.Replace("логгер", "логер");
                    oRet.Item1.Title = oRet.Item2.Subject;
                    //////description
                    oRet.Item2.Body = description.Replace("логгер", "логер");
                    oRet.Item1.About = oRet.Item2.Body;
                    //////
                    oRet.Item2.Attributes = new PostAttributeCollection
                    {
                        new PostAttribute(PostParams.MediaThumbnail, imagestoadd),
                        new PostAttribute(PostParams.Mood, channelTitle)//опять костыли, сохраняем имя канала в дополнительные параметры
                    };
                    //tags
                    if (!String.IsNullOrEmpty(tags))
                    {
                        oRet.Item2.Attributes.Add(new PostAttribute(PostParams.Tags, tags));
                    }

                    //geo
                    if (item["recordingDetails"] != null && item["recordingDetails"]["location"] != null)
                    {
                        if (item["recordingDetails"]["location"]["latitude"] != null
                            && item["recordingDetails"]["location"]["longitude"] != null)
                        {
                            var lat = item["recordingDetails"]["location"]["latitude"].Value<double>();
                            var lng = item["recordingDetails"]["location"]["longitude"].Value<double>();
                            if (Math.Abs(lat) > 1 && Math.Abs(lng) > 1)
                            {
                                oRet.Item2.Attributes.Add(new PostAttribute(PostParams.Location,
                                    String.Format(new CultureInfo("en-US"),
                                        "{0} {1}", lat, lng)));
                            }
                        }
                        else if (item["recordingDetails"]["locationDescription"] != null
                            && item["recordingDetails"]["locationDescription"].Value<string>().Contains(","))
                        {
                            //"recordingDetails": {
                            //    "locationDescription": "20.593684,78.96288",
                            //    "location": {
                            //     "altitude": 0.0
                            //    },
                            oRet.Item2.Attributes.Add(new PostAttribute(PostParams.Location,
                                    item["recordingDetails"]["locationDescription"].Value<string>().Replace(","," ")));
                        }

                    }
                    //duration
                    if (item["contentDetails"] != null)
                    {
                        //PT3H56M25S
                        var dur = item["contentDetails"]["duration"].Value<string>();
                        Match m = Regex.Match(dur, @"(?:(\d+)H)?(?:(\d+)M)?(\d+)S", Regexoptions);
                        if (m.Success)
                        {
                            var len = TimeSpan.FromSeconds(int.Parse(m.Groups[3].Value));
                            if (m.Groups[2].Success)
                            {
                                len += TimeSpan.FromMinutes(int.Parse(m.Groups[2].Value));
                            }
                            if (m.Groups[1].Success)
                            {
                                len += TimeSpan.FromHours(int.Parse(m.Groups[1].Value));
                            }
                            oRet.Item1.Duration = (uint) len.TotalSeconds;
                            oRet.Item2.Attributes.Add(
                                new PostAttribute(PostParams.Length, oRet.Item1.Duration));
                        }
                    }
                }
                if (item["snippet"]["liveBroadcastContent"] != null)
                {
                    oRet.Item1.IsLive = item["snippet"]["liveBroadcastContent"].Value<string>().Contains("live");
                }
            }
            if (item["status"]?["uploadStatus"] != null)
            {
                var status = item["status"]?["uploadStatus"].Value<string>();
                //processed / uploaded
                oRet.Item2.Existance = String.Compare(status, "processed", StringComparison.OrdinalIgnoreCase) == 0
                    || String.Compare(status, "uploaded", StringComparison.OrdinalIgnoreCase) == 0;
            }
            //topicDetails
            if(item["topicDetails"]?["topicCategories"] != null)
            {
                string topics = String.Empty;
                foreach(var t in item["topicDetails"]["topicCategories"])
                {
                    var t2 = t.Value<string>().ToLower();
                    if (t2.Contains("/wiki/"))
                    {
                        topics += t2.Split(new[] { "/wiki/" },StringSplitOptions.RemoveEmptyEntries)[1] + ",";
                    }
                }
                oRet.Item2.SetParam(PostParams.Categories, topics.TrimEnd(','));
            }
            oRet.Item2.Published = oRet.Item1.Published;
        }

        /// <summary>
        /// Формирование HTML кода для вставки в пост
        /// </summary>
        /// <param name="link"></param>
        /// <returns></returns>
        public string FormEmbedCode(Uri link, String adds = null)
        {
            string username;
            BlogType bt;
            if (ValidateLink(link.OriginalString,out username,out bt))
            {
                string embedlink = GetYoutubeCanonical(username, true);
                //само видео
                return String.Format(
                    "<iframe width=\"640\" height=\"360\" src=\"//{0}\" frameborder=\"0\" allowfullscreen></iframe>",
                    Common.DelHttp(embedlink));
                //return String.Format(
                //    "<object width=\"640\" height=\"360\"><param name=\"movie\" value=\"{0}\"></param><param name=\"allowFullScreen\" value=\"true\"></param><param name=\"allowScriptAccess\" value=\"always\"></param><embed src=\"{0}\" type=\"application/x-shockwave-flash\" allowfullscreen=\"true\" allowScriptAccess=\"always\" width=\"640\" height=\"360\"></embed></object>",
                //    embedlink);
            }
            return String.Empty;
        }

        /// <summary>
        /// Ссылка на видео на ютубе стандартная
        /// </summary>
        /// <param name="code">код ролика</param>
        /// <param name="embed">используется для вставки в пост?</param>
        /// <returns></returns>
        public static string GetYoutubeCanonical(string code, bool embed = false)
        {
            if (embed)
            {
                return String.Format("http://youtube.com/embed/{0}", code);
            }else
            {
                return String.Format("http://youtube.com/watch?v={0}", code);
            }
        }

        #endregion

        /// <summary>
        /// Получение комментариев от видео ютуба
        /// </summary>
        /// <param name="link">ссылка на видео</param>
        /// <returns></returns>
        public List<BlogPost> GetComments(Uri link, DateTime? fromTime = null)
        {
            List<BlogPost> oRet = new List<BlogPost>();
            try
            {
                if (String.IsNullOrEmpty(APIkey)) throw new ArgumentNullException("GooglePlus API key at config section");
                Match v = rYoutube.Match(link.OriginalString);
                if (v.Success)
                {
                    string code = v.Groups[2].Value;
                    string pageToken = null;
                    do
                    {
                        //pageToken=Cg0QgrP95qOiywIgACgBEhQIABDw8bOhv53LAhiAwZGiy5TLAhgCIAIo9OeG-PjbkJwz
                        Uri postlink;
                        if (
                            Uri.TryCreate(
                                String.Format(
                                    "https://www.googleapis.com/youtube/v3/commentThreads?part=snippet%2Creplies&maxResults=100&order=time&videoId={0}&key={1}{2}",
                                    code, APIkey,
                                    pageToken==null?"":"&pageToken="+pageToken//постраничная промотка
                                    ), UriKind.Absolute, out postlink))
                        {
                            pageToken = null;
                            using (var client = new WebClient {Encoding = Encoding.UTF8})
                            {
                                string reply = client.DownloadString(postlink);
                                var data = JObject.Parse(reply);
                                if (data["nextPageToken"] != null)
                                {
                                    pageToken = data["nextPageToken"].Value<string>();
                                }
                                //поехали добавлять....
                                var items = data["items"];
                                foreach (var item in items)
                                {
                                    //"snippet" , "replies"
                                    if (item["snippet"] != null)
                                    {
                                       var bp =  ProcessYoutubeComment(item["snippet"]);
                                        if (fromTime == null || bp.Published > fromTime)
                                        {
                                            oRet.Add(bp);
                                        }
                                        else
                                        {
                                            //промотали уже, дальше не идем
                                            pageToken = null;
                                        }
                                    }
                                    if (item["replies"] != null)
                                    {
                                        foreach (var replyComments in item["replies"]["comments"])
                                        {
                                            var bp = ProcessYoutubeComment(replyComments);
                                            if (fromTime == null || bp.Published > fromTime)
                                            {
                                                oRet.Add(bp);
                                            }
                                        }
                                    }
                                }
                            }
                        }
                    } while (!String.IsNullOrEmpty(pageToken) && oRet.Count < 1000);
                }
            }
            catch (Exception e1)
            {
                Debug.Write(e1);
            }
            return oRet;
        }

        /// <summary>
        /// Парсим комментарий
        /// </summary>
        /// <param name="item">JSON для парсинга</param>
        private BlogPost ProcessYoutubeComment(JToken item)
        {
            var topLevelComment = item["topLevelComment"] ?? item;
            var snippet = topLevelComment["snippet"] ?? topLevelComment;
            var bp = BlogPost.SimpleLink(GetYoutubeCanonical(snippet["videoId"].Value<string>()) + "&lc=" + topLevelComment["id"]);
            bp.SpecialParam = snippet["authorChannelId"]["value"].Value<string>();
            bp.Links = snippet["likeCount"].Value<int>();
            bp.Published = snippet["updatedAt"].Value<DateTime>();
            bp.Subject = snippet["authorDisplayName"].Value<string>();
            bp.Body = snippet["textDisplay"].Value<string>();
            return bp;
        }

        #region Public Methods

        /// <summary>
        /// Получение прямой ссылки на скачивание видео
        /// </summary>
        /// <param name="ylink">ссылка YouTube</param>
        /// <param name="title">обязательное имя ролика</param>
        /// <param name="videoFormat"></param>
        /// <returns></returns>
        public static string DirectLink(Uri ylink, string title, params VideoFormat[] videoFormat)
        {
            Match m = rYoutube.Match(ylink.OriginalString);
            if (m.Success)
            {
                return DirectLink(m.Groups[2].Value,title, true, videoFormat);
            }
            return String.Empty;
        }

        /// <summary>
        /// Получение прямой ссылки на скачивание видео
        /// </summary>
        /// <param name="idcode">код YouTube</param>
        /// <param name="videoFormat"></param>
        /// <returns></returns>
        public static string DirectLink(string idcode, string title, bool anyMp4IfNotFound = true, params VideoFormat[] videoFormat)
        {
            string link = string.Empty;
            try
            {
                HttpWebRequest req = Common.MakeRequest(
                    String.Format("http://www.youtube.com/get_video_info?video_id={0}", idcode));
                string flashvars = new StreamReader(req.GetResponse().GetResponseStream()).ReadToEnd();
                string _url = string.Empty;
                //Crazy string!
                _url = Uri.UnescapeDataString(flashvars);
                string[] urls = _url.Split(',').Where(i => i.Contains("&url=")).ToArray();

                NameValueCollection[] ParsedUrls = urls.Select(HttpUtility.ParseQueryString).ToArray();

                if (videoFormat != null && ParsedUrls.Length > 0)
                {
                    foreach (var parsedLink in ParsedUrls)
                    {
                        if(videoFormat.Any(v=> parsedLink["url"].Contains("itag="+(int)v)))
                        {
                            link = parsedLink["url"] + "&signature=" + parsedLink["sig"] + "&title=" + HttpUtility.UrlEncode(title);
                            break;
                        }
                    }
                    if (String.IsNullOrEmpty(link) && anyMp4IfNotFound)
                    {
                        //mime=video/mp4
                        var parsedLink = ParsedUrls.FirstOrDefault(
                            i =>
                                String.Compare(i["mime"], "video/mp4", StringComparison.InvariantCultureIgnoreCase) == 0);
                        if (parsedLink != null)
                        {
                            link = parsedLink["url"] + "&signature=" + parsedLink["sig"] + "&title=" + HttpUtility.UrlEncode(title);
                        }
                    }
                }
                else if(ParsedUrls.Length > 0)
                {
                    link = urls[0];
                }
            }
            catch (Exception e1)
            {
                Debug.Write(e1);
            }
            return link;
        }

        #endregion
    }
}